3. Plotting

Matplotlib is a standard plotting library of python. We begin by importing it first along with numpy.

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

The most widely used function in matplotlib is plot, which allows you to plot 1D and 2D data. Here is a simple example:

# Compute the x and y coordinates for points on a sine curve
x = np.arange(0, 3 * np.pi, 0.1)
y = np.sin(x)

# Plot the points using matplotlib
plt.plot(x, y)
[<matplotlib.lines.Line2D at 0x7f55061a4210>]
../_images/intro2viz_4_1.png

We can easily customize the style of plots for poster/talk/paper

print(plt.style.available)
['Solarize_Light2', '_classic_test_patch', 'bmh', 'classic', 'dark_background', 'fast', 'fivethirtyeight', 'ggplot', 'grayscale', 'seaborn', 'seaborn-bright', 'seaborn-colorblind', 'seaborn-dark', 'seaborn-dark-palette', 'seaborn-darkgrid', 'seaborn-deep', 'seaborn-muted', 'seaborn-notebook', 'seaborn-paper', 'seaborn-pastel', 'seaborn-poster', 'seaborn-talk', 'seaborn-ticks', 'seaborn-white', 'seaborn-whitegrid', 'tableau-colorblind10']
plt.style.use(['seaborn-white'])
plt.plot(x, y)
[<matplotlib.lines.Line2D at 0x7f55060494d0>]
../_images/intro2viz_7_1.png

if we want to customize plots it is better to plot by first defining fig and ax objecs which have manuy methods for customizing figure resolution and plot related aspects respecticely.

fig, ax = plt.subplots()

y_sin = np.sin(x)
y_cos = np.cos(x)

# Plot the points using matplotlib
ax.plot(x, y_sin)
ax.plot(x, y_cos)

# Specify labels
ax.set_xlabel('x axis label')
ax.set_ylabel('y axis label')
ax.set_title('Sine and Cosine')
ax.legend(['Sine', 'Cosine'])

#fig.savefig("myfig.pdf")
<matplotlib.legend.Legend at 0x7f5506005e10>
../_images/intro2viz_9_1.png

3.2. Pandas and seaborn! a power couple for multivariate statistics visualization

# Normal distribution of points 
n_points=200

df = pd.DataFrame({ 'X':    1*np.random.randn(n_points), 
                    'Y':    5*np.random.randn(n_points), 
                    'Z':    1+5*np.random.randn(n_points),
                    'time': np.linspace(0,n_points,n_points)
                  })
sns.jointplot(data=df, x='X', y='Z',
              kind="kde", cmap='RdBu_r')
<seaborn.axisgrid.JointGrid at 0x7f54f4930850>
../_images/intro2viz_29_1.png

3.3. Interactive plots

import plotly.express as px

3.3.1. Plotly

  • Plotly is large multi-language interactive graphing library that covers Python/Julia/R.

  • Plotly-dash is a framework for building web dashborads with itneractive plotly graphs.

  • Plotly-express is a high level library for quick visualizations whihc is similiar to seaborn vs matploltib in its philosophy

Check out this cool website built using Dash-Plotly

df = pd.DataFrame({ 'X':    1*np.random.randn(500), 
                    'Y':    5*np.random.randn(500), 
                    'Z':    1+5*np.random.randn(500),
                    'time':  np.arange(500)
                  })

px.density_heatmap(df, x='X', y='Y')
#px.line(df, x='X', y='Y')
#px.scatter(df, x='X', y='Y')
#px.area(df, x='X', y='Y')
#px.histogram(df, x="X")
fig = px.scatter(df, x="X", y="Y", size=20*np.ones(len(df)), 
                 animation_frame="time", animation_group='Y', color='Y',
                 range_x=[-20,20], range_y=[-20,20]
                 )
fig.show()

3.3.2. Holoviews

Stop plotting your data - annotate your data and let it visualize itself

Too many libraries for visualization in pyviz universe? Enter Holoviews! We all can agree there are just too many options for visualizing data and sometimes it is impossible to pick one and stick with becaue different libaries have different strengths when it comes visualizing different kinds of data. Plotly does a gread job with 3D visualization while matploltib is mostly desinged for 2D plots. Seaborn has everying one needs to genreate statistical plots while plotly may cover ony a subgroup of seaborn, etc. One emerging idea in scientific software desing is to create library agnostic tools. E.g if you want to plot histogram you can do it either using several different libraries or using a library agnsotic tool by specifiying the particular library interface for the visualization.

import holoviews as hv

hv.extension('matplotlib') # try 'bokeh' or 'matplotlib'
data = [1,2,4,8,16,25]

#hv.Curve(data) + hv.Scatter(data) # adding crates subplots
hv.Curve(data) * hv.Scatter(data) # multiplying creates overlay
import numpy as np
import pandas as pd
df = pd.DataFrame({'X':np.random.randn(1000),'Y':np.random.randn(1000), 'Z':np.random.randn(1000)})

data = df[['X', 'Y']].values

#hv.Curve(data)
hv.Scatter(data) + hv.Curve(data) + hv.Points(data) + hv.Bivariate(data)
#hv.Histogram(df['X'], density=True, bins=50)
#Example of 3D plot

Z = np.sin( np.random.randn(40,40) )

hv.Surface(Z, bounds=(-5, -5, 5, 5))

3.3.3. Using interactive widgets

Suppose we would like to explore how the variation of parameter \(\lambda\) affects the following function of a standing wave:

\[f(x) = sin (\omega *x +p)\]
  • Make a python-function which creates a plot as a function of a parameter(s) of interest.

  • Add an interactive widget on top to vary the parameter.

from ipywidgets import widgets
@widgets.interact(phase=(0,2*np.pi), freq = (0.1,5))  
def wave(phase=0, freq=0.5):          
    
    x  = np.linspace(0,10,1000)
    y  = np.sin(freq*x+phase)
        
    plt.plot(x, y)
hv.extension('matplotlib')

def wave(phase, freq):
    
    x  = np.linspace(0,10,1000)
    y  = np.sin(freq*x+phase)
    
    return hv.Curve((x, y))


## Option-1: Define ranges of variables and pass to DynamicMap
#hv.DynamicMap(wave, kdims=['phase', 'freq']).redim.range(phase=(0.1,2*np.pi), freq=(0.1,5))

## Option-2: make dictionary of data points and pass to HoloMap
hv.HoloMap({ (phase, freq): wave(phase, freq) 
            for phase in np.linspace(0.0,2*np.pi,20) 
            for freq in np.linspace(0.1, 2, 20) }, 
            kdims=['phase', 'freq'])
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/PIL/ImageFile.py in _save(im, fp, tile, bufsize)
    499     try:
--> 500         fh = fp.fileno()
    501         fp.flush()

AttributeError: '_idat' object has no attribute 'fileno'

During handling of the above exception, another exception occurred:

KeyboardInterrupt                         Traceback (most recent call last)
<ipython-input-25-eaa909fdcca0> in <module>
     16             for phase in np.linspace(0.0,2*np.pi,20)
     17             for freq in np.linspace(0.1, 2, 20) }, 
---> 18             kdims=['phase', 'freq'])

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/IPython/core/displayhook.py in __call__(self, result)
    260             self.start_displayhook()
    261             self.write_output_prompt()
--> 262             format_dict, md_dict = self.compute_format_data(result)
    263             self.update_user_ns(result)
    264             self.fill_exec_result(result)

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/IPython/core/displayhook.py in compute_format_data(self, result)
    149 
    150         """
--> 151         return self.shell.display_formatter.format(result)
    152 
    153     # This can be set to True by the write_output_prompt method in a subclass

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/IPython/core/formatters.py in format(self, obj, include, exclude)
    148             return {}, {}
    149 
--> 150         format_dict, md_dict = self.mimebundle_formatter(obj, include=include, exclude=exclude)
    151 
    152         if format_dict or md_dict:

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/decorator.py in fun(*args, **kw)
    230             if not kwsyntax:
    231                 args, kw = fix(args, kw, sig)
--> 232             return caller(func, *(extras + args), **kw)
    233     fun.__name__ = func.__name__
    234     fun.__doc__ = func.__doc__

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/IPython/core/formatters.py in catch_format_error(method, self, *args, **kwargs)
    222     """show traceback on failed format call"""
    223     try:
--> 224         r = method(self, *args, **kwargs)
    225     except NotImplementedError:
    226         # don't warn on NotImplementedErrors

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/IPython/core/formatters.py in __call__(self, obj, include, exclude)
    968 
    969             if method is not None:
--> 970                 return method(include=include, exclude=exclude)
    971             return None
    972         else:

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/holoviews/core/dimension.py in _repr_mimebundle_(self, include, exclude)
   1315         combined and returned.
   1316         """
-> 1317         return Store.render(self)
   1318 
   1319 

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/holoviews/core/options.py in render(cls, obj)
   1403         data, metadata = {}, {}
   1404         for hook in hooks:
-> 1405             ret = hook(obj)
   1406             if ret is None:
   1407                 continue

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/holoviews/ipython/display_hooks.py in pprint_display(obj)
    280     if not ip.display_formatter.formatters['text/plain'].pprint:
    281         return None
--> 282     return display(obj, raw_output=True)
    283 
    284 

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/holoviews/ipython/display_hooks.py in display(obj, raw_output, **kwargs)
    256     elif isinstance(obj, (HoloMap, DynamicMap)):
    257         with option_state(obj):
--> 258             output = map_display(obj)
    259     elif isinstance(obj, Plot):
    260         output = render(obj)

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/holoviews/ipython/display_hooks.py in wrapped(element)
    144         try:
    145             max_frames = OutputSettings.options['max_frames']
--> 146             mimebundle = fn(element, max_frames=max_frames)
    147             if mimebundle is None:
    148                 return {}, {}

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/holoviews/ipython/display_hooks.py in map_display(vmap, max_frames)
    204         return None
    205 
--> 206     return render(vmap)
    207 
    208 

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/holoviews/ipython/display_hooks.py in render(obj, **kwargs)
     66         renderer = renderer.instance(fig='png')
     67 
---> 68     return renderer.components(obj, **kwargs)
     69 
     70 

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/holoviews/plotting/renderer.py in components(self, obj, fmt, comm, **kwargs)
    408                 doc = Document()
    409                 with config.set(embed=embed):
--> 410                     model = plot.layout._render_model(doc, comm)
    411                 if embed:
    412                     return render_model(model, comm)

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/panel/viewable.py in _render_model(self, doc, comm)
    433                         save_path=config.embed_save_path,
    434                         load_path=config.embed_load_path,
--> 435                         progress=False)
    436         else:
    437             add_to_doc(model, doc)

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/panel/io/embed.py in embed_state(panel, model, doc, max_states, max_opts, json, json_prefix, save_path, load_path, progress, states)
    331                 with always_changed(config.safe_embed):
    332                     for w in ws:
--> 333                         w.value = k
    334             except Exception:
    335                 skip = True

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/param/parameterized.py in _f(self, obj, val)
    316             instance_param.__set__(obj, val)
    317             return
--> 318         return f(self, obj, val)
    319     return _f
    320 

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/param/parameterized.py in __set__(self, obj, val)
    934                       old=_old, new=val, type=None)
    935         for watcher in watchers:
--> 936             obj.param._call_watcher(watcher, event)
    937         if not obj.param._BATCH_WATCH:
    938             obj.param._batch_call_watchers()

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/param/parameterized.py in _call_watcher(self_, watcher, event)
   1589             event = self_._update_event_type(watcher, event, self_.self_or_cls.param._TRIGGER)
   1590             with batch_watch(self_.self_or_cls, enable=watcher.queued, run=False):
-> 1591                 self_._execute_watcher(watcher, (event,))
   1592 
   1593     def _batch_call_watchers(self_):

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/param/parameterized.py in _execute_watcher(self, watcher, events)
   1571             async_executor(partial(watcher.fn, *args, **kwargs))
   1572         else:
-> 1573             watcher.fn(*args, **kwargs)
   1574 
   1575     def _call_watcher(self_, watcher, event):

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/panel/pane/holoviews.py in _widget_callback(self, event)
    215     def _widget_callback(self, event):
    216         for _, (plot, pane) in self._plots.items():
--> 217             self._update_plot(plot, pane)
    218 
    219     #----------------------------------------------------------------

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/panel/pane/holoviews.py in _update_plot(self, plot, pane)
    208             plot.update(key)
    209             if hasattr(plot.renderer, 'get_plot_state'):
--> 210                 pane.object = plot.renderer.get_plot_state(plot)
    211             else:
    212                 # Compatibility with holoviews<1.13.0

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/param/parameterized.py in _f(self, obj, val)
    316             instance_param.__set__(obj, val)
    317             return
--> 318         return f(self, obj, val)
    319     return _f
    320 

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/param/parameterized.py in __set__(self, obj, val)
    934                       old=_old, new=val, type=None)
    935         for watcher in watchers:
--> 936             obj.param._call_watcher(watcher, event)
    937         if not obj.param._BATCH_WATCH:
    938             obj.param._batch_call_watchers()

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/param/parameterized.py in _call_watcher(self_, watcher, event)
   1589             event = self_._update_event_type(watcher, event, self_.self_or_cls.param._TRIGGER)
   1590             with batch_watch(self_.self_or_cls, enable=watcher.queued, run=False):
-> 1591                 self_._execute_watcher(watcher, (event,))
   1592 
   1593     def _batch_call_watchers(self_):

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/param/parameterized.py in _execute_watcher(self, watcher, events)
   1571             async_executor(partial(watcher.fn, *args, **kwargs))
   1572         else:
-> 1573             watcher.fn(*args, **kwargs)
   1574 
   1575     def _call_watcher(self_, watcher, event):

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/panel/pane/base.py in _update_pane(self, *events)
    183             if comm or state._unblocked(doc):
    184                 with unlocked():
--> 185                     self._update_object(ref, doc, root, parent, comm)
    186                 if comm and 'embedded' not in root.tags:
    187                     push(doc, comm)

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/panel/pane/base.py in _update_object(self, ref, doc, root, parent, comm)
    148         old_model = self._models[ref][0]
    149         if self._updates:
--> 150             self._update(ref, old_model)
    151         else:
    152             new_model = self._get_model(doc, root, parent, comm)

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/panel/pane/plot.py in _update(self, ref, model)
    191     def _update(self, ref=None, model=None):
    192         if not self.interactive:
--> 193             model.update(**self._get_properties())
    194             return
    195         manager = self._managers[ref]

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/panel/pane/image.py in _get_properties(self)
    100         if self.object is None:
    101             return dict(p, text='<img></img>')
--> 102         data = self._img()
    103         if not isinstance(data, bytes):
    104             data = base64.b64decode(data)

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/panel/pane/plot.py in _img(self)
    218             bbox_inches = None
    219 
--> 220         self.object.canvas.print_figure(b, bbox_inches=bbox_inches)
    221         return b.getvalue()
    222 

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/matplotlib/backend_bases.py in print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
   2259                         orientation=orientation,
   2260                         bbox_inches_restore=_bbox_inches_restore,
-> 2261                         **kwargs)
   2262             finally:
   2263                 if bbox_inches and restore_bbox:

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/matplotlib/backend_bases.py in wrapper(*args, **kwargs)
   1667             kwargs.pop(arg)
   1668 
-> 1669         return func(*args, **kwargs)
   1670 
   1671     return wrapper

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/matplotlib/backends/backend_agg.py in print_png(self, filename_or_obj, metadata, pil_kwargs, *args)
    509         mpl.image.imsave(
    510             filename_or_obj, self.buffer_rgba(), format="png", origin="upper",
--> 511             dpi=self.figure.dpi, metadata=metadata, pil_kwargs=pil_kwargs)
    512 
    513     def print_to_buffer(self):

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/matplotlib/image.py in imsave(fname, arr, vmin, vmax, cmap, format, origin, dpi, metadata, pil_kwargs)
   1614         pil_kwargs.setdefault("format", format)
   1615         pil_kwargs.setdefault("dpi", (dpi, dpi))
-> 1616         image.save(fname, **pil_kwargs)
   1617 
   1618 

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/PIL/Image.py in save(self, fp, format, **params)
   2170 
   2171         try:
-> 2172             save_handler(self, fp, filename)
   2173         finally:
   2174             # do what we can to clean up

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/PIL/PngImagePlugin.py in _save(im, fp, filename, chunk, save_all)
   1333         _write_multiple_frames(im, fp, chunk, rawmode)
   1334     else:
-> 1335         ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)])
   1336 
   1337     if info:

/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/PIL/ImageFile.py in _save(im, fp, tile, bufsize)
    512             else:
    513                 while True:
--> 514                     l, s, d = e.encode(bufsize)
    515                     fp.write(d)
    516                     if s:

KeyboardInterrupt: 

3.3.4. Interactive visualization of a Reaction-Diffusion system

def laplacian(Z, dx):
    """
    Function to computes the discrete Laplace operator of
    a 2D variable on the grid (using a five-point stencil
    finite difference method.)
    """
    Ztop = Z[0:-2,1:-1]
    Zleft = Z[1:-1,0:-2]
    Zbottom = Z[2:,1:-1]
    Zright = Z[1:-1,2:]
    Zcenter = Z[1:-1,1:-1]
    return (Ztop + Zleft + Zbottom + Zright - 4 * Zcenter) / dx**2


def reaction_diffusion(a=2.8e-4, b=5e-3, tau=0.1, k=-0.005, samples=10):
    """
    We simulate the PDE with the finite difference method.

    The samples value is the number of equally spaced samples
    to collect over the total simulation time T.
    """
    size = 100         # size of the 2D grid
    dx = 2./size       # space step
    T = 10.0           # total time
    dt = 4.5 * dx**2    # simulation time step
    n = int(T/dt)

    result = []
    U = np.random.rand(size, size)
    V = np.random.rand(size, size)

    sample_times = [int(el) for el in np.linspace(0, n, samples)]

    for i in range(n):
        # We compute the Laplacian of u and v.
        deltaU = laplacian(U, dx=dx)
        deltaV = laplacian(V, dx=dx)
        # We take the values of u and v inside the grid.
        Uc = U[1:-1,1:-1]
        Vc = V[1:-1,1:-1]
        # We update the variables.
        U[1:-1,1:-1], V[1:-1,1:-1] = \
            Uc + dt * (a * deltaU + Uc - Uc**3 - Vc + k), \
            Vc + dt * (b * deltaV + Uc - Vc) / tau
        # Neumann conditions: derivatives at the edges
        # are null.
        for Z in (U, V):
            Z[0,:] = Z[1,:]
            Z[-1,:] = Z[-2,:]
            Z[:,0] = Z[:,1]
            Z[:,-1] = Z[:,-2]

        if i in sample_times:
            result.append((i * dt,U.copy()))
            
    return result
sim1 = reaction_diffusion()
hv.HoloMap({time: hv.Image(array) for (time, array) in sim1}, kdims=['Time'])

3.4. Additional resoruces.

Matplotlib has a huge scientific user base. This means that you can always find a good working template of any kind of visualization which you want to make. With basic understanding of matplotlib and some solid googling skills you can go very far. Here are some additional resources that you may find helpful